package com.neihe.service.impl;

import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.json.JSONObject;
import com.aliyun.oss.OSS;
import com.aliyun.oss.common.utils.BinaryUtil;
import com.aliyun.oss.model.GeneratePresignedUrlRequest;
import com.aliyun.oss.model.MatchMode;
import com.aliyun.oss.model.PolicyConditions;
import com.aliyun.oss.model.ResponseHeaderOverrides;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.neihe.common.base.ApiException;
import com.neihe.common.base.ResultCode;
import com.neihe.common.constant.Const;
import com.neihe.common.constant.FileConst;
import com.neihe.common.enums.DelFlagEnum;
import com.neihe.common.enums.FileTypeEnum;
import com.neihe.common.enums.FolderFlagEnum;
import com.neihe.common.utils.FileUtils;
import com.neihe.config.OssConfig;
import com.neihe.entity.File;
import com.neihe.entity.UserFile;
import com.neihe.entity.dto.file.*;
import com.neihe.entity.vo.file.BreadcrumbVO;
import com.neihe.entity.vo.file.FolderTreeNodeVO;
import com.neihe.entity.vo.file.UserFileVO;
import com.neihe.entity.vo.user.UseSpaceVO;
import com.neihe.listenner.event.RecycleFileEvent;
import com.neihe.mapper.UserFileMapper;
import com.neihe.service.IFileService;
import com.neihe.service.IUserFileService;
import com.neihe.service.IUserService;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.servlet.http.HttpServletRequest;
import java.net.URL;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import static com.neihe.common.constant.FileConst.*;

/**
 * <p>
 * 用户文件信息表 服务实现类
 * </p>
 *
 * @author neihe
 * @since 2025-01-05
 */
@Service
@Slf4j
public class UserFileServiceImpl extends ServiceImpl<UserFileMapper, UserFile> implements IUserFileService, ApplicationContextAware {

    @Autowired
    private IFileService fileService;

    @Autowired
    private OssConfig ossConfig;

    @Autowired
    private OSS ossClient;

    @Autowired
    private IUserService userService;

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Setter
    private ApplicationContext applicationContext;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Map<String, String> getUploadSign(Long fileSize) {
        long userId = StpUtil.getLoginIdAsLong();

        // 检查容量
        UseSpaceVO useSpace = userService.getUseSpace(userId);
        Long totalSpace = useSpace.getTotalSpace();
        Long use = useSpace.getUseSpace();
        Long temp = use + fileSize;

        if (totalSpace < temp) {
            throw new ApiException(ResultCode.NEED_EXPAND);
        }

        // 文件存储目录
        String dir = "uploads/" + userId + "/" + LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy/MM/dd"));

        // 签名有效期
        long expireTime = 60;
        long expireEndTime = System.currentTimeMillis() + expireTime * 1000;
        Date expirationTime = new Date(expireEndTime);

        // 最大上传文件大小 1gb
        long maxSize = 1024 * 1024 * 1024;

        try {
            JSONObject jasonCallback = new JSONObject();
            jasonCallback.set("callbackUrl", ossConfig.getCallbackUrl());
            jasonCallback.set("callbackBody", "object=${object}&size=${size}&mimeType=${mimeType}" +
                    "&userId=${x:uid}&identifier=${x:identifier}&parentId=${x:pid}");
            jasonCallback.set("callbackBodyType", "application/x-www-form-urlencoded");

            String base64CallbackBody = BinaryUtil.toBase64String(jasonCallback.toString().getBytes());

            // 创建 PostPolicy，设置策略过期时间
            PolicyConditions policyConds = new PolicyConditions();
            policyConds.addConditionItem(PolicyConditions.COND_CONTENT_LENGTH_RANGE, 0, maxSize);
            policyConds.addConditionItem(MatchMode.StartWith, PolicyConditions.COND_KEY, dir);

            // 生成 Policy
            String postPolicy = ossClient.generatePostPolicy(expirationTime, policyConds);
            byte[] binaryData = postPolicy.getBytes(StandardCharsets.UTF_8);
            String encodedPolicy = BinaryUtil.toBase64String(binaryData);

            // 签名 Policy
            String postSignature = ossClient.calculatePostSignature(postPolicy);

            // 返回结果
            Map<String, String> respMap = new LinkedHashMap<>();
            respMap.put("accessId", ossConfig.getAccessKeyId());
            respMap.put("policy", encodedPolicy);
            respMap.put("signature", postSignature);
            respMap.put("callback", base64CallbackBody);
            respMap.put("dir", dir);
            respMap.put("host", ossConfig.getEndpoint());
            return respMap;
        } catch (Exception e) {
            log.error("获取上传凭证失败：{}", e.getMessage());
            throw new ApiException("获取上传凭证失败");
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public String skipUpload(UploadFileDTO requestParam) {
        String filename = requestParam.getFilename();
        String identifier = requestParam.getIdentifier();
        Long userId = StpUtil.getLoginIdAsLong();
        try {
            // 查询用户的文件列表
            List<File> fileList = fileService.getFileList(userId, identifier);
            if (CollUtil.isNotEmpty(fileList)) {
                File record = fileList.get(Const.ZERO_INT);
                saveUserFile(
                        requestParam.getParentId(),
                        filename,
                        FolderFlagEnum.NO,
                        FileTypeEnum.getFileTypeCode(FileUtils.getFileSuffix(filename)),
                        record.getFileId(),
                        userId,
                        record.getFileSize(),
                        record.getFileSizeDesc()
                );
                userService.deductSpace(userId, requestParam.getFileSize());
                return "文件秒传";
            }

        } catch (Exception e) {
            throw new ApiException(ResultCode.NEED_EXPAND);
        }
        return "普通上传";
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void ossCallback(HttpServletRequest request) {
        Long userId = Long.valueOf(request.getParameter("userId"));
        // 文件预览的响应头Content-Type的值
        String mimeType = request.getParameter("mimeType");
        // 文件存储位置
        String realPath = request.getParameter("object");
        // 文件大小
        Long fileSize = Long.valueOf(request.getParameter("size"));
        String identifier = request.getParameter("identifier");
        Long parentId = Long.valueOf(request.getParameter("parentId"));
        String filename = realPath.substring(realPath.lastIndexOf("/") + 1);

        try {
            // 保存文件记录
            File fileRecord = fileService.saveFile(
                    filename,
                    realPath,
                    fileSize,
                    identifier,
                    mimeType,
                    userId
            );

            // 保存用户文件关联
            saveUserFile(
                    parentId,
                    filename,
                    FolderFlagEnum.NO,
                    FileTypeEnum.getFileTypeCode(FileUtils.getFileSuffix(filename)),
                    fileRecord.getFileId(),
                    userId,
                    fileSize,
                    fileRecord.getFileSizeDesc()
            );

            // 扣减用户的空间容量
            userService.deductSpace(userId, fileRecord.getFileSize());
        } catch (Exception e) {
            log.error("保存上传文件失败: {}", e.getMessage());
        }
    }

    @Override
    public List<UserFileVO> getFileList(QueryFileListContextDTO context) {
        return baseMapper.selectFileList(context);
    }

    @Override
    public List<BreadcrumbVO> getBreadcrumbs(Long fileId, Long userId) {
        List<UserFile> folderRecords = queryFolderRecords(userId);
        Map<Long, BreadcrumbVO> prepareBreadcrumbVOMap = folderRecords.stream().map(BreadcrumbVO::transfer).collect(Collectors.toMap(BreadcrumbVO::getId, a -> a));
        BreadcrumbVO currentNode;
        List<BreadcrumbVO> result = CollUtil.newArrayList();
        do {
            currentNode = prepareBreadcrumbVOMap.get(fileId);
            if (Objects.nonNull(currentNode)) {
                result.add(0, currentNode);
                fileId = currentNode.getParentId();
            }
        } while (Objects.nonNull(currentNode));
        return result;
    }

    @Override
    public Long createFolder(CreateFolderDTO requestParam) {
        return saveUserFile(requestParam.getParentId(),
                requestParam.getFolderName(),
                FolderFlagEnum.YES,
                null,
                null,
                requestParam.getUserId(),
                null,
                null
        );
    }

    @Override
    public UserFile getUserRootFile(Long userId) {
        QueryWrapper<UserFile> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("user_id", userId);
        queryWrapper.eq("parent_id", FileConst.TOP_PARENT_ID);
        queryWrapper.eq("del_flag", DelFlagEnum.NO.getCode());
        queryWrapper.eq("folder_flag", FolderFlagEnum.YES.getCode());
        return getOne(queryWrapper);
    }

    @Override
    public void updateFilename(UpdateFilenameDTO requestParam) {
        // 修改文件名称的条件校验
        Long fileId = requestParam.getFileId();
        UserFile entity = getById(fileId);

        // 文件ID是有效的
        if (entity == null) {
            throw new ApiException("该文件ID无效");
        }

        // 用户是否有权限更新该文件的文件名称
        long userId = StpUtil.getLoginIdAsLong();
        if (!Objects.equals(entity.getUserId(), userId)) {
            throw new ApiException("没有修改该文件名称的权限");
        }

        // 新旧文件名称不能一样
        String newFilename = requestParam.getNewFilename();
        if (Objects.equals(entity.getFilename(), newFilename)) {
            throw new ApiException("请换一个新的文件名称来修改");
        }

        // 不能使用当前文件夹下面的子文件的名称
        QueryWrapper<UserFile> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("parent_id", entity.getParentId());
        queryWrapper.eq("filename", newFilename);
        long count = count(queryWrapper);

        if (count > 0) {
            throw new ApiException("该文件名称已被占用");
        }

        // 执行文件重命名的操作
        entity.setFilename(newFilename);
        entity.setUpdateTime(new Date());

        if (!updateById(entity)) throw new ApiException("内部错误,文件重命名失败");

    }

    @Override
    public void recycleFile(RecycleFileDTO requestParam) {
        // 文件ID合法校验
        List<Long> fileIds = requestParam.getFileIds();

        List<UserFile> userFiles = listByIds(fileIds);
        if (userFiles.size() != fileIds.size()) {
            throw new ApiException("存在不合法的文件记录");
        }

        Set<Long> fileIdSet = userFiles.stream().map(UserFile::getFileId).collect(Collectors.toSet());
        int oldSize = fileIdSet.size();
        fileIdSet.addAll(fileIds);
        int newSize = fileIdSet.size();

        if (oldSize != newSize) {
            throw new ApiException("存在不合法的文件记录");
        }

        Set<Long> userIdSet = userFiles.stream().map(UserFile::getUserId).collect(Collectors.toSet());
        if (userIdSet.size() != 1) {
            throw new ApiException("存在不合法的文件记录");
        }

        // 校验用户对该文件的权限
        Long dbUserId = userIdSet.stream().findFirst().get();
        if (!Objects.equals(dbUserId, StpUtil.getLoginIdAsLong())) {
            throw new ApiException("没有该文件的权限");
        }

        UpdateWrapper<UserFile> updateWrapper = new UpdateWrapper<>();
        updateWrapper.in("file_id", fileIds);
        updateWrapper.set("del_flag", DelFlagEnum.YES.getCode());
        updateWrapper.set("update_time", new Date());
        if (!update(updateWrapper)) {
            throw new ApiException("内部错误,操作失败");
        }

        // 对外发布事件
        RecycleFileEvent event = new RecycleFileEvent(this, fileIds);
        applicationContext.publishEvent(event);
    }

    @Override
    public List<FolderTreeNodeVO> getFolderTree(Long userId) {
        List<UserFile> folderRecords = queryFolderRecords(userId);
        return assembleFolderTreeNode(folderRecords);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void transfer(TransferFileDTO requestParam) {
        // 目标文件必须是一个文件夹
        Long targetParentId = requestParam.getTargetParentId();
        if (!checkIsFolder(getById(targetParentId))) {
            throw new ApiException("目标文件不是一个文件夹");
        }

        // 选中的要转移的文件列表中不能含有目标文件夹以及其子文件夹
        List<Long> fileIds = requestParam.getFileIds();
        List<UserFile> prepareRecords = listByIds(fileIds);
        Long userId = StpUtil.getLoginIdAsLong();
        if (checkIsChildFolder(prepareRecords, targetParentId, userId)) {
            throw new ApiException("目标文件夹ID不能是选中文件列表的文件夹ID或其子文件夹ID");
        }

        // 执行文件转移的动作
        prepareRecords.forEach(record -> {
            record.setParentId(targetParentId);
            record.setUserId(userId);
            record.setCreateTime(new Date());
            record.setUpdateTime(new Date());
            handleDuplicateFilename(record);
        });

        if (!updateBatchById(prepareRecords)) {
            throw new ApiException("内部错误，文件转移失败");
        }
    }

    @Override
    public List<UserFile> findAllFileRecords(List<UserFile> records) {
        List<UserFile> result = CollUtil.newArrayList(records);
        if (CollUtil.isEmpty(result)) return result;
        long folderCount = result.stream()
                .filter(record -> Objects.equals(record.getFolderFlag(), FolderFlagEnum.YES.getCode()))
                .count();
        if (folderCount == 0) return result;
        records.forEach(record -> doFindAllChildRecords(result, record));
        return result;
    }

    @Override
    public List<UserFile> findAllFileRecordsByFileIdList(List<Long> fileIds) {
        if (CollectionUtils.isEmpty(fileIds)) {
            return CollUtil.newArrayList();
        }
        List<UserFile> records = listByIds(fileIds);
        if (CollectionUtils.isEmpty(records)) {
            return CollUtil.newArrayList();
        }
        return findAllFileRecords(records);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public String download(Long fileId, Long userId) {
        UserFile record = getById(fileId);
        checkOperatePermission(record, userId);
        return doDownload(record, userId);
    }

    @Override
    public String preview(Long fileId, Long userId) {
        UserFile record = getById(fileId);
        checkOperatePermission(record, userId);
        return doPreview(record);
    }

    /**
     * 文件预览
     */
    private String doPreview(UserFile record) {
        File realFileRecord = fileService.getById(record.getRealFileId());
        if (realFileRecord == null) {
            throw new ApiException("当前的文件记录不存在");
        }

        Long fileSize = realFileRecord.getFileSize();
        if (fileSize > FIFTEEN_MB) {
            throw new ApiException("文件过大，不支持预览");
        }

        // 有效时间
        long expireTime = 60;
        long expireEndTime = System.currentTimeMillis() + expireTime * 1000;
        Date expirationTime = new Date(expireEndTime);

        try {
            String realPath = realFileRecord.getRealPath();
            GeneratePresignedUrlRequest request = new GeneratePresignedUrlRequest(ossConfig.getBucketName(), realPath);
            request.setExpiration(expirationTime);

            String encodedFileName = URLEncoder.encode(record.getFilename(), "UTF-8");

            // 只设置 Content-Disposition，不设置 Content-Type
            ResponseHeaderOverrides responseHeaders = new ResponseHeaderOverrides();
            responseHeaders.setContentDisposition(String.format("inline;filename=%s;filename*=UTF-8''%s",
                    encodedFileName, encodedFileName));

            // 设置响应头
            request.setResponseHeaders(responseHeaders);

            URL url = ossClient.generatePresignedUrl(request);
            return url.toString();
        } catch (Exception e) {
            log.error("文件预览失败 {}", e.getMessage());
            throw new ApiException("文件预览失败");
        }
    }

    /**
     * 校验用户的操作权限
     */
    private void checkOperatePermission(UserFile record, Long userId) {
        if (record == null) {
            throw new ApiException("当前文件记录不存在");
        }

        if (!Objects.equals(record.getUserId(), userId)) {
            throw new ApiException("没有该文件的操作权限");
        }

        if (checkIsFolder(record)) {
            throw new ApiException("文件夹不支持该操作");
        }
    }

    /**
     * 文件下载（带限速）
     * <p>
     * 下载速度：一级：5Mb/s 二级：3Mb/s 三级：300Kb/s
     * 触发条件：每日流量 <= 100MB = 一级；每日流量 <= 100MB = 二级；每日流量 > 200MB = 三级
     */
    private String doDownload(UserFile record, Long userId) {
        File realFileRecord = fileService.getById(record.getRealFileId());
        if (realFileRecord == null) {
            throw new ApiException("当前的文件记录不存在");
        }

        // 获取用户当日流量key
        String date = DateUtil.format(new Date(), "yyyyMMdd");
        String redisKey = USER_DOWNLOAD_LIMIT + userId + ":" + date;

        // 获取并更新用户当日已下载流量
        Long usedTraffic = redisTemplate.opsForValue().increment(redisKey, 0);
        if (usedTraffic == null) {
            usedTraffic = 0L;
            // 设置24小时过期，实现次日重置
            redisTemplate.expire(redisKey, 24, TimeUnit.HOURS);
        }

        // 根据已用流量确定限速级别
        int trafficLimit;
        if (usedTraffic <= 100 * 1024 * 1024) { // <= 100MB
            trafficLimit = speed5MB;
        } else if (usedTraffic <= 200 * 1024 * 1024) { // <= 200MB
            trafficLimit = speed3MB;
        } else { // > 200MB
            trafficLimit = speed300KB;
        }

        // 有效时间
        long expireTime = 60;
        long expireEndTime = System.currentTimeMillis() + expireTime * 1000;
        Date expirationTime = new Date(expireEndTime);

        try {
            String realPath = realFileRecord.getRealPath();
            GeneratePresignedUrlRequest request = new GeneratePresignedUrlRequest(ossConfig.getBucketName(), realPath);
            request.setExpiration(expirationTime);

            // 设置下载时的文件名
            String encodedFileName = URLEncoder.encode(record.getFilename(), "UTF-8");

            // 将 Content-Disposition 设置为 attachment 强制下载
            ResponseHeaderOverrides responseHeaders = new ResponseHeaderOverrides();
            responseHeaders.setContentDisposition(String.format("attachment;filename=%s;filename*=UTF-8''%s",
                    encodedFileName, encodedFileName));

            // 设置响应头
            request.setResponseHeaders(responseHeaders);

            // 分级限速
            request.setTrafficLimit(trafficLimit);

            URL url = ossClient.generatePresignedUrl(request);

            // 更新用户流量使用记录（增加本次下载的文件大小）
            redisTemplate.opsForValue().increment(redisKey, record.getFileSize());

            return url.toString();
        } catch (Exception e) {
            log.error("文件下载失败 {}", e.getMessage());
            throw new ApiException("文件下载失败");
        }
    }

    /**
     * 递归查询所有的子文件列表
     */
    private void doFindAllChildRecords(List<UserFile> result, UserFile record) {
        if (record == null) return;
        if (!checkIsFolder(record)) return;
        List<UserFile> childRecords = findChildRecordsIgnoreDelFlag(record.getFileId());
        if (CollectionUtils.isEmpty(childRecords)) return;
        result.addAll(childRecords);
        childRecords.stream()
                .filter(childRecord -> FolderFlagEnum.YES.getCode().equals(childRecord.getFolderFlag()))
                .forEach(childRecord -> doFindAllChildRecords(result, childRecord));
    }

    /**
     * 查询文件夹下面的文件记录，忽略删除标识
     */
    private List<UserFile> findChildRecordsIgnoreDelFlag(Long fileId) {
        QueryWrapper<UserFile> queryWrapper = Wrappers.query();
        queryWrapper.eq("parent_id", fileId);
        return list(queryWrapper);
    }

    /**
     * 校验目标文件夹ID是都是要操作的文件记录的文件夹ID以及其子文件夹ID
     */
    private boolean checkIsChildFolder(List<UserFile> prepareRecords, Long targetParentId, Long userId) {
        // 如果要操作的文件列表中没有文件夹，那就直接返回false
        prepareRecords = prepareRecords.stream()
                .filter(record -> Objects.equals(record.getFolderFlag(), FolderFlagEnum.YES.getCode()))
                .collect(Collectors.toList());

        if (CollectionUtils.isEmpty(prepareRecords)) return false;

        // 拼装文件夹ID以及所有子文件夹ID，判断存在即可
        List<UserFile> folderRecords = queryFolderRecords(userId);
        Map<Long, List<UserFile>> folderRecordMap = folderRecords.stream()
                .collect(Collectors.groupingBy(UserFile::getParentId));

        List<UserFile> unavailableFolderRecords = CollUtil.newArrayList();
        unavailableFolderRecords.addAll(prepareRecords);

        prepareRecords.forEach(record -> findAllChildFolderRecords(unavailableFolderRecords, folderRecordMap, record));
        List<Long> unavailableFolderRecordIds = unavailableFolderRecords.stream()
                .map(UserFile::getFileId)
                .collect(Collectors.toList());
        return unavailableFolderRecordIds.contains(targetParentId);
    }

    /**
     * 查找文件夹的所有子文件夹记录
     */
    private void findAllChildFolderRecords(List<UserFile> unavailableFolderRecords, Map<Long, List<UserFile>> folderRecordMap, UserFile record) {
        if (record == null) return;
        List<UserFile> childFolderRecords = folderRecordMap.get(record.getFileId());

        if (CollectionUtils.isEmpty(childFolderRecords)) return;
        unavailableFolderRecords.addAll(childFolderRecords);
        childFolderRecords.forEach(childRecord -> findAllChildFolderRecords(unavailableFolderRecords, folderRecordMap, childRecord));
    }

    /**
     * 检查当前文件记录是不是一个文件夹
     */
    private boolean checkIsFolder(UserFile userFile) {
        if (userFile == null) {
            throw new ApiException("当前文件记录不存在");
        }
        return Objects.equals(FolderFlagEnum.YES.getCode(), userFile.getFolderFlag());
    }

    /**
     * 组装文件夹树节点
     *
     * @param folderRecords 文件夹记录集合
     * @return 结果
     */
    private List<FolderTreeNodeVO> assembleFolderTreeNode(List<UserFile> folderRecords) {
        if (CollUtil.isEmpty(folderRecords)) {
            return CollUtil.newArrayList();
        }

        List<FolderTreeNodeVO> folderTreeNodeList = folderRecords.stream()
                .map(this::UserFile2FolderTreeNode)
                .collect(Collectors.toList());

        Map<Long, List<FolderTreeNodeVO>> treeMap = folderTreeNodeList.stream()
                .collect(Collectors.groupingBy(FolderTreeNodeVO::getParentId));

        for (FolderTreeNodeVO node : folderTreeNodeList) {
            List<FolderTreeNodeVO> children = treeMap.get(node.getId());
            if (CollectionUtils.isNotEmpty(children)) {
                node.getChildren().addAll(children);
            }
        }

        return folderTreeNodeList.stream()
                .filter(node -> Objects.equals(node.getParentId(), FileConst.TOP_PARENT_ID))
                .collect(Collectors.toList());
    }

    private FolderTreeNodeVO UserFile2FolderTreeNode(UserFile record) {
        FolderTreeNodeVO vo = new FolderTreeNodeVO();
        vo.setLabel(record.getFilename());
        vo.setId(record.getFileId());
        vo.setChildren(CollUtil.newArrayList());
        vo.setParentId(record.getParentId());
        return vo;
    }


    /**
     * 保存用户文件的映射记录
     *
     * @param parentId       父文件ID
     * @param filename       文件名
     * @param folderFlagEnum 是否是文件夹
     * @param fileType       文件类型
     * @param realFileId     真是物理文件ID
     * @param userId         用户ID
     * @param fileSize       文件大小
     * @param fileSizeDesc   文件大小展示字符
     * @return 保存记录的文件ID
     */
    private Long saveUserFile(Long parentId,
                              String filename,
                              FolderFlagEnum folderFlagEnum,
                              Integer fileType,
                              Long realFileId,
                              Long userId,
                              Long fileSize,
                              String fileSizeDesc) {
        UserFile entity = assembleUserFile(parentId, userId, filename, folderFlagEnum,
                fileType, realFileId, fileSize, fileSizeDesc);
        if (!save((entity))) {
            throw new ApiException("内部错误，保存文件信息失败");
        }
        return entity.getFileId();
    }

    /**
     * 用户文件映射关系实体转化
     */
    private UserFile assembleUserFile(Long parentId, Long userId, String filename,
                                      FolderFlagEnum folderFlagEnum, Integer fileType,
                                      Long realFileId, Long fileSize, String fileSizeDesc) {
        UserFile entity = new UserFile();
        entity.setFileId(null);
        entity.setUserId(userId);
        entity.setParentId(parentId);
        entity.setRealFileId(realFileId);
        entity.setFilename(filename);
        entity.setFolderFlag(folderFlagEnum.getCode());
        entity.setFileSize(fileSize);
        entity.setFileSizeDesc(fileSizeDesc);
        entity.setFileType(fileType);
        entity.setDelFlag(DelFlagEnum.NO.getCode());
        entity.setCreateTime(new Date());
        entity.setUpdateTime(new Date());
        // 处理文件命名一致的问题
        handleDuplicateFilename(entity);
        return entity;
    }

    /**
     * 处理同一文件夹下面有文件名称重复
     */
    private void handleDuplicateFilename(UserFile entity) {
        // 文件名
        String filename = entity.getFilename();

        // 不带后缀的新文件名
        String newFilenameWithoutSuffix;

        // 新文件名后缀
        String newFilenameSuffix;

        // 找到最后一个 '.' 字符的位置
        int newFilenamePointPosition = filename.lastIndexOf(Const.POINT_STR);

        // 如果没有找到 '.' 字符，说明没有扩展名
        if (newFilenamePointPosition == Const.MINUS_ONE_INT) {
            //  没有扩展名，返回整个文件名和空后缀
            newFilenameWithoutSuffix = filename;
            newFilenameSuffix = "";
        } else {
            // 提取文件名主体
            newFilenameWithoutSuffix = filename.substring(Const.ZERO_INT, newFilenamePointPosition);
            // 提取文件后缀
            newFilenameSuffix = filename.replace(newFilenameWithoutSuffix, "");
        }

        List<UserFile> existRecords = getDuplicateFilename(entity, newFilenameWithoutSuffix);
        if (CollUtil.isEmpty(existRecords)) {
            return;
        }

        List<String> existFilenames = existRecords.stream().map(UserFile::getFilename).collect(Collectors.toList());

        // 按照系统级规则重命名文件
        int count = 1;
        String newFilename;

        // 如果新生成的文件名已经存在，则通过计数器累加生成新的文件名，直到这个新的文件名不冲突位置
        do {
            newFilename = assembleNewFilename(newFilenameWithoutSuffix, count, newFilenameSuffix);
            count++;
        } while (existFilenames.contains(newFilename));
        entity.setFilename(newFilename);
    }

    /**
     * 组装新的文件名
     * <p>
     * 拼装规则：test（1）.png、test（2）.png
     *
     * @param baseName          文件名
     * @param count             计数器
     * @param newFilenameSuffix 文件扩展名
     * @return 新的文件名
     */
    private String assembleNewFilename(String baseName, int count, String newFilenameSuffix) {
        return baseName + "(" + count + ")" + newFilenameSuffix;
    }

    /**
     * 查找同一文件下同名文件
     *
     * @param entity                   用户文件信息
     * @param newFilenameWithoutSuffix 文件名
     * @return 同名文件集合
     */
    private List<UserFile> getDuplicateFilename(UserFile entity, String newFilenameWithoutSuffix) {
        QueryWrapper<UserFile> userFileQueryWrapper = new QueryWrapper<>();
        userFileQueryWrapper.eq("parent_id", entity.getParentId());
        userFileQueryWrapper.eq("folder_flag", entity.getFolderFlag());
        userFileQueryWrapper.eq("user_id", entity.getUserId());
        userFileQueryWrapper.eq("del_flag", DelFlagEnum.NO.getCode());
        userFileQueryWrapper.likeRight("filename", newFilenameWithoutSuffix);
        return list(userFileQueryWrapper);
    }

    /**
     * 查询用户所有有效的文件夹信息
     *
     * @param userId 用户ID
     * @return 结果
     */
    private List<UserFile> queryFolderRecords(Long userId) {
        QueryWrapper<UserFile> queryWrapper = Wrappers.query();
        queryWrapper.eq("user_id", userId);
        queryWrapper.eq("folder_flag", FolderFlagEnum.YES.getCode());
        queryWrapper.eq("del_flag", DelFlagEnum.NO.getCode());
        return list(queryWrapper);
    }
}
